Completed
Pull Request — develop (#75)
by
unknown
01:04
created

forcegraph.js ➔ ... ➔ d3Drag.subject   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
c 1
b 0
f 0
nc 2
dl 0
loc 11
rs 9.4285
nop 0
1
define(['d3-selection', 'd3-force', 'd3-zoom', 'd3-drag', 'forcegraph/math', 'forcegraph/draw'], function (d3Selection, d3Force, d3Zoom, d3Drag, math, draw) {
2
  'use strict';
3
4
  return function (config, linkScale, sidebar, router) {
5
    var self = this;
6
    var el;
7
    var canvas;
8
    var ctx;
9
    var force;
10
    var forceLink;
11
12
    var transform = d3Zoom.zoomIdentity;
13
    var intNodes = [];
14
    var dictNodes = {};
15
    var intLinks = [];
16
17
    const NODE_RADIUS_DRAG = 10;
18
    const NODE_RADIUS_SELECT = 15;
19
    const LINK_RADIUS_SELECT = 12;
20
21
    const ZOOM_MIN = 1 / 4;
22
    const ZOOM_MAX = 3;
23
24
    draw.setTransform(transform);
25
26
    function resizeCanvas() {
27
      canvas.width = el.offsetWidth;
28
      canvas.height = el.offsetHeight;
29
      canvas.style.width = el.offsetWidth + 'px';
30
      canvas.style.height = el.offsetHeight + 'px';
31
    }
32
33
    function moveTo(x, y) {
34
      transform.x = (canvas.width + sidebar()) / 2  - x * transform.k;
35
      transform.y =  canvas.height / 2              - y * transform.k;
36
    }
37
38
    function onClick() {
39
      if (d3Selection.event.defaultPrevented) {
40
        return;
41
      }
42
43
      var e = transform.invert([d3Selection.event.clientX, d3Selection.event.clientY]);
44
      var n = force.find(e[0], e[1], NODE_RADIUS_SELECT);
45
46
      if (n !== undefined) {
47
        router.node(n.o.node)();
48
        return;
49
      }
50
51
      e = {x: e[0], y: e[1]};
52
53
54
      var closedLink;
55
      var radius = LINK_RADIUS_SELECT;
56
      intLinks
57
        /* Disable Clickable VPN
58
        .filter(function (d) {
59
          return d.o.type !== 'fastd' && d.o.type !== 'L2TP';
60
        })
61
        */
62
        .forEach(function (d) {
63
          var distance = math.distanceLink(e, d.source, d.target);
64
          if (distance < radius) {
65
            closedLink = d;
66
            radius = distance;
67
          }
68
        });
69
70
      if (closedLink !== undefined) {
71
        router.link(closedLink.o)();
72
      }
73
    }
74
75
    function redraw() {
76
      ctx.save();
77
      ctx.clearRect(0, 0, canvas.width, canvas.height);
78
      ctx.translate(transform.x, transform.y);
79
      ctx.scale(transform.k, transform.k);
80
81
      intLinks.forEach(draw.drawLink);
82
      intNodes.forEach(draw.drawNode);
83
84
      ctx.restore();
85
    }
86
87
    el = document.createElement('div');
88
    el.classList.add('graph');
89
90
    forceLink = d3Force.forceLink()
91
     .distance(function (d) {
92
       if (d.o.type === 'fastd' || d.o.type === 'L2TP') {
93
         return 0;
94
       }
95
       return 75;
96
     })
97
     .strength(function (d) {
98
       if (d.o.type === 'fastd' || d.o.type === 'L2TP') {
99
         return 0.02;
100
       }
101
       return Math.max(0.5, 1 / d.o.tq);
102
     });
103
104
    var zoom = d3Zoom.zoom()
105
         .scaleExtent([ZOOM_MIN, ZOOM_MAX])
106
         .on('zoom', function () {
107
           transform = d3Selection.event.transform;
108
           draw.setTransform(transform);
109
           redraw();
110
         });
111
112
113
    force = d3Force.forceSimulation()
114
      .force('link', forceLink)
115
      .force('charge', d3Force.forceManyBody())
116
      .on('tick', redraw);
117
118
    var drag = d3Drag.drag()
119
      .subject(function () {
120
        var e = transform.invert([d3Selection.event.x, d3Selection.event.y]);
121
        var n = force.find(e[0], e[1], NODE_RADIUS_DRAG);
122
123
        if (n !== undefined) {
124
          n.x = transform.applyX(n.x);
125
          n.y = transform.applyY(n.y);
126
          return n;
127
        }
128
        return undefined;
129
      })
130
      .on('start', function () {
131
        if (!d3Selection.event.active) {
132
          force.alphaTarget(0.1).restart();
133
        }
134
        d3Selection.event.subject.fx = transform.invertX(d3Selection.event.subject.x);
135
        d3Selection.event.subject.fy = transform.invertY(d3Selection.event.subject.y);
136
      })
137
      .on('drag', function () {
138
        d3Selection.event.subject.fx = transform.invertX(d3Selection.event.x);
139
        d3Selection.event.subject.fy = transform.invertY(d3Selection.event.y);
140
      })
141
      .on('end', function () {
142
        if (!d3Selection.event.active) {
143
          force.alphaTarget(0);
144
        }
145
        d3Selection.event.subject.fx = null;
146
        d3Selection.event.subject.fy = null;
147
      });
148
149
    canvas = d3Selection.select(el)
150
      .append('canvas')
151
      .on('click', onClick)
152
      .call(drag)
153
      .call(zoom)
154
      .node();
155
156
    ctx = canvas.getContext('2d');
157
    draw.setCTX(ctx);
158
159
    window.addEventListener('resize', function () {
160
      resizeCanvas();
161
      redraw();
162
    });
163
164
    self.setData = function setData(data) {
165
      intNodes = data.graph.nodes.map(function (d) {
166
        var e;
167
        if (d.id in dictNodes) {
168
          e = dictNodes[d.id];
169
        } else {
170
          e = {};
171
          dictNodes[d.id] = e;
172
        }
173
174
        e.o = d;
175
176
        return e;
177
      });
178
179
      intLinks = data.graph.links.map(function (d) {
180
        var e = {};
181
        e.o = d;
182
        e.source = dictNodes[d.source.id];
183
        e.target = dictNodes[d.target.id];
184
        e.color = linkScale(d.tq).hex();
185
186
        return e;
187
      });
188
189
      force.nodes(intNodes);
190
      forceLink.links(intLinks);
191
192
      force.restart();
193
      resizeCanvas();
194
    };
195
196
    self.resetView = function resetView() {
197
      draw.setHighlight(null);
198
      transform.k = (ZOOM_MIN + 1) / 2;
199
      moveTo( (canvas.width / 2) - sidebar(), canvas.height / 2);
200
      redraw();
201
    };
202
203
    self.gotoNode = function gotoNode(d) {
204
      draw.setHighlight({ type: 'node', o: d });
205
206
      for (var i = 0; i < intNodes.length; i++) {
207
        var n = intNodes[i];
208
        if ( n.o.node !== d) {
209
          continue;
210
        }
211
        transform.k = (ZOOM_MAX + 1) / 2;
212
        moveTo(n.x, n.y);
213
        break;
214
      }
215
      redraw();
216
    };
217
218
    self.gotoLink = function gotoLink(d) {
219
      draw.setHighlight({ type: 'link', o: d });
220
      for (var i = 0; i < intLinks.length; i++) {
221
        var l = intLinks[i];
222
        if ( l.o !== d) {
223
          continue;
224
        }
225
        moveTo( (l.source.x + l.target.x) / 2, (l.source.y + l.target.y) / 2);
226
        break;
227
      }
228
      redraw();
229
    };
230
231
    self.destroy = function destroy() {
232
      force.stop();
233
      canvas.remove();
234
      force = null;
235
236
      if (el.parentNode) {
237
        el.parentNode.removeChild(el);
238
      }
239
    };
240
241
    self.render = function render(d) {
242
      d.appendChild(el);
243
      resizeCanvas();
244
    };
245
246
    return self;
247
  };
248
});
249